//
//  ConduitsManager.m
//  Task Views
//
//  Created by Chris Karr on 2/23/09.
//  Copyright 2009 Chris J. Karr. All rights reserved.
//

#import <Security/Security.h>

#import "ConduitsManager.h"

#import "BasecampConduit.h"
#import "iCalConduit.h"
#import "ThingsConduit.h"
#import "EntourageConduit.h"
#import "FogBugzConduit.h"

@implementation ConduitsManager

- (Conduit *) conduitForDefinition:(NSDictionary *) conduitDef
{
	if ([[conduitDef valueForKey:@"type"] isEqual:@"Basecamp"])
	{
		BasecampConduit * conduit = [[BasecampConduit alloc] init];
		
		return [conduit autorelease];
	}
	else if ([[conduitDef valueForKey:@"type"] isEqual:@"iCal"])
	{
		iCalConduit * conduit = [[iCalConduit alloc] init];
		
		return [conduit autorelease];
	}
	else if ([[conduitDef valueForKey:@"type"] isEqual:@"Things"])
	{
		ThingsConduit * conduit = [[ThingsConduit alloc] init];
		
		return [conduit autorelease];
	}
	else if ([[conduitDef valueForKey:@"type"] isEqual:@"Entourage"])
	{
		EntourageConduit * conduit = [[EntourageConduit alloc] init];
		
		return [conduit autorelease];
	}
	else if ([[conduitDef valueForKey:@"type"] isEqual:@"FogBugz"])
	{
		FogBugzConduit * conduit = [[FogBugzConduit alloc] init];
		
		return [conduit autorelease];
	}
	else
	{
		NSLog (@"Unknown conduit: %@", [conduitDef valueForKey:@"type"]);
		return nil;
	}
}

- (Conduit *) currentConduit
{
	return [self conduitForDefinition:[[conduits selectedObjects] lastObject]];
}

- (IBAction) conduits:(id) sender
{
	[conduitsWindow makeKeyAndOrderFront:sender];
}

- (IBAction) add:(id) sender
{
	NSMutableDictionary * newConduit = [NSMutableDictionary dictionary];
	
	[newConduit setValue:@"New Conduit" forKey:@"name"];
	[newConduit setValue:@"Select Conduit Type" forKey:@"type"];
	[newConduit setValue:@"" forKey:@"site"];
	[newConduit setValue:@"" forKey:@"username"];

	[conduits addObject:newConduit];
	[conduits setSelectedObjects:[NSArray arrayWithObject:newConduit]];
}

- (void) awakeFromNib
{
	[conduits addObserver:self forKeyPath:@"selection" options:0 context:NULL];
}

- (void) observeValueForKeyPath: (NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context
{
	[self willChangeValueForKey:@"password"];
	
	[self didChangeValueForKey:@"password"];
}

- (NSString *) passwordForDefinition:(NSDictionary *) def
{
	NSString * kcPassword = nil;
	
	if (def != nil)
	{
		UInt32 length = 256;
		void * password = NULL;
	
		NSString * name = [NSString stringWithFormat:@"Task Views: %@: %@", [def valueForKey:@"type"], [def valueForKey:@"site"], nil];
		const char * utf8 = [name UTF8String];
	
		NSString * username = [def valueForKey:@"username"];
		const char * user = [username UTF8String];
	
		OSStatus status = SecKeychainFindGenericPassword (NULL, strlen (utf8), utf8, strlen (user), user, &length, &password, NULL);
	
		if (status == errSecItemNotFound)
			NSLog (@"No password. returning nil.");
		else if (status	== noErr)
		{
			NSData * passwordData = [NSData dataWithBytes:password length:length];
		
			kcPassword = [[[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding] autorelease];
		}
		else
			NSLog (@"error retrieving keychain password %d", status);
	
		SecKeychainItemFreeContent (NULL, password);
	}
	
	return kcPassword;
}

- (NSString *) password
{
	NSDictionary * def = [[conduits selectedObjects] lastObject];
	
	return [self passwordForDefinition:def];
}

- (void) setPassword:(NSString *) newPassword
{
	if (newPassword == nil)
		newPassword = @"";
	
	NSDictionary * def = [[conduits selectedObjects] lastObject];

	if (def != nil)
	{
		NSString * name = [NSString stringWithFormat:@"Task Views: %@: %@", [def valueForKey:@"type"], [def valueForKey:@"site"], nil];
		const char * utf8 = [name UTF8String];
		
		NSString * username = [def valueForKey:@"username"];
		const char * user = [username UTF8String];
		
		if ([self password] == nil)
			SecKeychainAddGenericPassword (NULL, strlen (utf8), utf8, strlen (user), user, [newPassword lengthOfBytesUsingEncoding:NSUTF8StringEncoding], 
										   [newPassword UTF8String], NULL);
		else
		{
			SecKeychainItemRef itemRef = NULL;
		
			SecKeychainFindGenericPassword (NULL, strlen (utf8), utf8, strlen (user), user, 0, NULL, &itemRef);
		
			SecKeychainItemModifyAttributesAndData (itemRef, NULL, [newPassword lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [newPassword UTF8String]);
		}
	}
}

- (IBAction) remove:(id) sender
{
	NSDictionary * conduit = [[conduits selectedObjects] lastObject];
	
	NSString * title = [NSString stringWithFormat:@"Delete '%@'?", [conduit valueForKey:@"name"]];
	NSString * message = [NSString stringWithFormat:@"Are you sure that you wish to delete the conduit '%@'?", [conduit valueForKey:@"name"]];
	
	if (NSAlertDefaultReturn == NSRunAlertPanel(title, message, @"Yes", @"No", nil))
		[conduits removeObject:conduit];
}

- (IBAction) openExternalSite:(id) sender
{
	id conduit = [self currentConduit];
	[conduit openExternalSite:[[conduits selectedObjects] lastObject]];
}

- (NSArray *) tasksWithField:(NSString *) field matching:(NSObject *) value
{
	NSMutableArray * matches = [NSMutableArray array];

	for (NSDictionary * task in [tasks arrangedObjects])
	{
		if ([[task valueForKey:field] isEqual:value])
			[matches addObject:task];
	}
	
	return matches;
}

- (BOOL) taskExists:(NSDictionary *) options identifier:(NSString *) identifier
{
	NSArray * matches = [self tasksWithField:identifier matching:[options valueForKey:identifier]];
	
	if ([matches count] > 0)
		return YES;

	return NO;
}

- (void) mergeTask:(NSDictionary *) options identifier:(NSString *) identifier definition:(NSDictionary *) definition 
		   replace:(BOOL) doReplace
{
	NSDictionary * mapping = [definition valueForKey:@"mappings"];
	
	if (mapping == nil)
		mapping = [NSDictionary dictionary];

	NSArray * matches = [self tasksWithField:identifier matching:[options valueForKey:identifier]];
	
	for (NSMutableDictionary * task in matches)
	{
		NSEnumerator * keys = [[options allKeys] objectEnumerator];
		NSString * key = nil;
		while (key = [keys nextObject])
		{
			NSObject * defValue = [options valueForKey:key];

			if (doReplace)
			{
				if ([key isEqual:@"name"])
					[task setValue:[defValue description] forKey:@"name"];
				else if ([key isEqual:@"source"])
					[task setValue:[defValue description] forKey:@"source"];

				NSString * mapKey = [mapping valueForKey:key];
				if (mapKey != nil)
					key = mapKey;
			}

			if (doReplace || [task valueForKey:key] == nil)
				[task setValue:defValue forKey:key];
		}
	}
}

- (void) refreshConduit:(Conduit *) conduit definition:(NSDictionary *) definition
{
	NSDictionary * mapping = [definition valueForKey:@"mappings"];
	
	if (mapping == nil)
		mapping = [NSDictionary dictionary];
	
	NSMutableDictionary * def = [NSMutableDictionary dictionaryWithDictionary:definition];
	[def setValue:[self passwordForDefinition:def] forKey:@"password"];
	
	NSArray * fetchedTasks = [conduit fetchTasks:def];

	for (NSDictionary * newTask in fetchedTasks)
	{
		if (![self taskExists:newTask identifier:[conduit identifier]])
		{
			NSMutableDictionary * task = [NSMutableDictionary dictionary];
			
			[task setValue:[newTask valueForKey:@"name"] forKey:@"name"];
			[task setValue:[newTask valueForKey:@"source"] forKey:@"source"];
			
			for (NSString * key in [newTask allKeys])
			{
				NSObject * defValue = [newTask valueForKey:key];
				NSString * mapKey = [mapping valueForKey:key];
				
				if (mapKey != nil)
					key = mapKey;
					
				[task setValue:defValue forKey:key];
			}
			
			[tasks addObject:task];
		}
		else
		{
			[self mergeTask:newTask identifier:[conduit identifier] definition:definition
					replace:[[[[conduits selectedObjects] lastObject] valueForKey:@"replace"] boolValue]];
		}
	}	

	[[NSNotificationCenter defaultCenter] postNotificationName:@"refresh_fields" object:nil];
}

- (void) updateMappings:(NSMutableDictionary *) def withConduit:(Conduit *) conduit
{
	NSMutableDictionary * mappings = [def valueForKey:@"mappings"];
	
	if (mappings == nil)
		mappings = [NSMutableDictionary dictionary];

	for (NSDictionary * field in [fields arrangedObjects])
	{
		NSString * name = [field valueForKey:@"name"];
		
		NSString * value = [mappings valueForKey:name];
		
		if (value == nil)
			[mappings setValue:name forKey:name];
	}
	
	[def setValue:mappings forKey:@"mappings"];
}

- (IBAction) refresh:(id) sender
{
	Conduit * conduit = [self currentConduit];
	NSMutableDictionary * def = [[conduits selectedObjects] lastObject];

	NSWindow * keyWindow = [NSApp keyWindow];
	
	if (keyWindow != nil)
	{
		[progressBar setUsesThreadedAnimation:YES];
		[progressBar startAnimation:sender];
		[progressMessage setStringValue:[NSString stringWithFormat:@"Refreshing %@...", [def valueForKey:@"name"], nil]];

		[NSApp beginSheet:progressWindow modalForWindow:keyWindow modalDelegate:self didEndSelector:nil contextInfo:NULL];
	}

	[self refreshConduit:conduit definition:def];
	[self updateMappings:def withConduit:conduit];

	if (keyWindow != nil)
	{
		[NSApp endSheet:progressWindow];
		[progressWindow orderOut:self];
		[progressBar stopAnimation:sender];
	}
}

- (IBAction) refreshAll:(id) sender
{
	NSEnumerator * defIter = [[conduits arrangedObjects] objectEnumerator];
	NSMutableDictionary * def = nil;
	while (def = [defIter nextObject])
	{
		Conduit * conduit = [self conduitForDefinition:def];
		
		NSWindow * keyWindow = [NSApp keyWindow];
		
		if (keyWindow != nil)
		{
			[progressBar setUsesThreadedAnimation:YES];
			[progressBar startAnimation:sender];
			[progressMessage setStringValue:[NSString stringWithFormat:@"Refreshing %@...", [def valueForKey:@"name"], nil]];
			
			[NSApp beginSheet:progressWindow modalForWindow:keyWindow modalDelegate:self didEndSelector:nil contextInfo:NULL];
		}
		
		[self refreshConduit:conduit definition:def];
		[self updateMappings:def withConduit:conduit];
		
		if (keyWindow != nil)
		{
			[NSApp endSheet:progressWindow];
			[progressWindow orderOut:self];
			[progressBar stopAnimation:sender];
		}
	}
}

- (IBAction) finishMapping:(id) sender
{
	[NSApp endSheet:mappingWindow];
	[mappingWindow orderOut:self];
}

- (IBAction) showMapping:(id) sender
{
	[NSApp beginSheet:mappingWindow modalForWindow:conduitsWindow modalDelegate:self didEndSelector:nil contextInfo:NULL];
}

@end
